﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Extented.UI.Core.Native
{
    public class FREInheritedProperties
    {
        #region internalClasses
        enum FREInheritedPropertyValueSource { Default = 0x1, Inherited = 0x2, Local = 0x4, Coerced = 0x8 }
        class FREInheritedPropertyValue
        {
            static readonly object unsetValue = null;
            readonly FREInheritedPropertyRecord record;
            readonly FREInheritedProperties owner;
            readonly object key;
            object cachedCoercedValue;
            object cachedInheritedValue;
            object cachedLocalValue;
            object cachedValue;
            public FREInheritedPropertyValue(FREInheritedPropertyRecord record, FREInheritedProperties owner, object key)
            {
                this.record = record;
                this.owner = owner;
                this.cachedCoercedValue = record.DefaultValue;
                this.cachedInheritedValue = record.DefaultValue;
                this.cachedLocalValue = record.DefaultValue;
                this.cachedValue = record.DefaultValue;
                this.key = key;
                ValueSource = FREInheritedPropertyValueSource.Default;
            }
            public FREInheritedPropertyValueSource ValueSource { get; set; }
            public object Key { get { return key; } }
            public object GetValue() { return cachedValue; }
            public void SetValue(object value) { SetCurrentValue(value, FREInheritedPropertyValueSource.Local); }
            public void ClearValue() { SetCurrentValue(unsetValue, FREInheritedPropertyValueSource.Default); }
            void SetCurrentValue(object value, FREInheritedPropertyValueSource valueSource)
            {
                var oldValue = cachedValue;
                if (Equals(unsetValue, value))
                {
                    cachedLocalValue = record.DefaultValue;
                    valueSource = FREInheritedPropertyValueSource.Default;
                }
                if (valueSource.HasFlag(FREInheritedPropertyValueSource.Default))
                {
                    cachedValue = cachedLocalValue;
                }
                if (valueSource.HasFlag(FREInheritedPropertyValueSource.Inherited))
                {
                    cachedInheritedValue = value;
                    cachedValue = cachedInheritedValue;
                }
                if (valueSource.HasFlag(FREInheritedPropertyValueSource.Local))
                {
                    cachedLocalValue = value;
                    cachedValue = cachedLocalValue;
                }
                if (record.CoerceValueCallback != null)
                {
                    cachedCoercedValue = record.CoerceValueCallback(owner.context, value);
                    if (!Equals(cachedCoercedValue, value))
                    {
                        valueSource |= FREInheritedPropertyValueSource.Coerced;
                    }
                    cachedValue = cachedCoercedValue;
                }
                ValueSource = valueSource;
                ValueChanged(oldValue, cachedValue);
            }
            void ValueChanged(object oldValue, object newValue)
            {
                if (CheckCoerceValue())
                    return;
                if (!Equals(oldValue, newValue))
                {
                    if (record.PropertyChangedCallback != null)
                        record.PropertyChangedCallback(owner.context, oldValue, newValue);
                    PropagateValue();
                }
            }
            public void CoerceValue()
            {
                object value = unsetValue;
                if (ValueSource.HasFlag(FREInheritedPropertyValueSource.Default) || ValueSource.HasFlag(FREInheritedPropertyValueSource.Local))
                    value = cachedLocalValue;
                if (ValueSource.HasFlag(FREInheritedPropertyValueSource.Inherited))
                    value = cachedInheritedValue;
                SetCurrentValue(value, ValueSource);
            }
            bool CheckCoerceValue()
            {
                if (ValueSource.HasFlag(FREInheritedPropertyValueSource.Default))
                {
                    var parent = owner.context.Parent;
                    if (parent != null)
                    {
                        var entry = parent.InheritedProperties.GetOrCreateValueEntry(this.Key);
                        if (!entry.ValueSource.HasFlag(FREInheritedPropertyValueSource.Default) || entry.ValueSource.HasFlag(FREInheritedPropertyValueSource.Coerced))
                        {
                            SetCurrentValue(entry.cachedValue, FREInheritedPropertyValueSource.Inherited);
                            return true;
                        }
                    }
                }
                return false;
            }
            void PropagateValue()
            {
                var context = (IFrameworkRenderElementContext)owner.context;
                if (context == null)
                    return;
                for (int i = 0; i < context.RenderChildrenCount; i++)
                {
                    var child = context.GetRenderChild(i);
                    var childProperty = child.InheritedProperties.GetOrCreateValueEntry(Key);
                    if (childProperty.ValueSource.HasFlag(FREInheritedPropertyValueSource.Local))
                        continue;
                    childProperty.SetCurrentValue(GetValue(), FREInheritedPropertyValueSource.Inherited);
                    childProperty.PropagateValue();
                }
            }
        }
        struct FREInheritedPropertyRecord
        {
            public object DefaultValue { get; set; }
            public Action<FrameworkRenderElementContext, object, object> PropertyChangedCallback { get; set; }
            public Func<FrameworkRenderElementContext, object, object> CoerceValueCallback { get; set; }
        }
        #endregion
        #region static
        static readonly Dictionary<object, FREInheritedPropertyRecord> records;
        static FREInheritedProperties()
        {
            records = new Dictionary<object, FREInheritedPropertyRecord>();
        }
        public static object Register(object defaultValue = null, Action<FrameworkRenderElementContext, object, object> propertyChangedCallback = null, Func<FrameworkRenderElementContext, object, object> coerceValueCallback = null)
        {
            object key = new object();
            records.Add(key, new FREInheritedPropertyRecord()
            {
                DefaultValue = defaultValue,
                PropertyChangedCallback = propertyChangedCallback,
                CoerceValueCallback = coerceValueCallback
            });
            return key;
        }
        #endregion
        readonly Dictionary<object, FREInheritedPropertyValue> values;
        readonly FrameworkRenderElementContext context;
        public FREInheritedProperties(FrameworkRenderElementContext context)
        {
            this.values = new Dictionary<object, FREInheritedPropertyValue>();
            this.context = context;
        }
        public void SetValue(object key, object value)
        {
            var currentValue = GetOrCreateValueEntry(key);
            currentValue.SetValue(value);
        }
        public object GetValue(object key)
        {
            return GetOrCreateValueEntry(key).GetValue();
        }
        FREInheritedPropertyValue GetOrCreateValueEntry(object key)
        {
            FREInheritedPropertyValue value;
            if (!values.TryGetValue(key, out value))
            {
                FREInheritedPropertyRecord record;
                if (!records.TryGetValue(key, out record))
                {
                    throw new KeyNotFoundException("key");
                }
                value = new FREInheritedPropertyValue(record, this, key);
                values.Add(key, value);
            }
            return value;
        }
        public void CoerceValue(object key)
        {
            GetOrCreateValueEntry(key).CoerceValue();
        }
        public void CoerceValue()
        {
            foreach (var key in records.Keys)
                CoerceValue(key);
        }
    }
}
